// 
// SoapTrace.Net - A soap message monitoring utility. 
// 
// Copyright (C) 2004-2009 Matthew Ward
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
// 
// Matthew Ward (mrward@users.sourceforge.net)
// 

using SoapTrace.Core.Communication;
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace SoapTrace.Tests.Utils
{
	/// <summary>
	/// A mock remote server end point.  This object will be 
	/// created by the proxy server class.
	/// </summary>
	public class MockRemoteServer
	{
		/// <summary>
		/// The tcp listener.
		/// </summary>
		private TcpListener m_Listener;
		
		/// <summary>
		/// The thread that processes the client connections.
		/// </summary>
		private Thread m_Thread;
		
		/// <summary>
		/// Status flag indicating whether this server is currently running.
		/// </summary>
		private bool m_Running;
		
		/// <summary>
		/// The connected clients.
		/// </summary>
		private SocketCollection m_Clients = new SocketCollection( );
		
		/// <summary>
		/// The Client connection event.
		/// </summary>
		private AutoResetEvent m_ConnectionEvent = new AutoResetEvent( false );
		
		/// <summary>
		/// A client message arrived event.
		/// </summary>
		private AutoResetEvent m_MessageArrivedEvent = new AutoResetEvent( false );
		
		/// <summary>
		/// A client disconnection event.
		/// </summary>
		private AutoResetEvent m_DisconnectionEvent = new AutoResetEvent( false );
		
		/// <summary>
		/// The client message received.
		/// </summary>
		private MockRemoteServerMessage m_MessageReceived = 
			new MockRemoteServerMessage( );
		
		/// <summary>
		/// Creates a new instance of this class.
		/// </summary>
		public MockRemoteServer( )
		{
		}
		
		/// <summary>
		/// Starts the mock server.
		/// </summary>
		/// <param name="port">The server's listening port.</param>
		public void Start( int port )
		{
			StartListening( port );
			
			StartProcessingThread( );
		}

		/// <summary>
		/// Stops the server.
		/// </summary>
		public void Stop( )
		{
			try
			{
				StopProcessingThread( );

				DisconnectAllClients( );

				StopListening( );
			}
			catch( Exception Ex )
			{
				Debug.WriteLine( String.Concat( "MockRemoteServer::Stop ",
					Ex.Message ) );
			}
		}
		
		/// <summary>
		/// Waits for a client connection.
		/// </summary>
		/// <param name="timeout">A timeout in milliseconds.</param>
		/// <returns>A status flag indicating whether a connection occured
		/// before timing out.</returns>
		public bool WaitForConnection( int timeout )
		{
			return m_ConnectionEvent.WaitOne( timeout, true );
		}
		
		/// <summary>
		/// Waits for a client disconnection.
		/// </summary>
		/// <param name="timeout">A timeout in milliseconds.</param>
		/// <returns>A status flag indicating whether a disconnection occured
		/// before timing out.</returns>
		public bool WaitForDisconnection( int timeout )
		{
			return m_DisconnectionEvent.WaitOne( timeout, true );
		}
		
		/// <summary>
		/// Waits for a client message to arrive.
		/// </summary>
		/// <param name="message">The message received whilst waiting.</param>
		/// <param name="timeout">A timeout in milliseconds.</param>
		/// <returns>A status flag indicating whether a message arrived
		/// before timing out.</returns>
		public bool WaitForMessage( out string message, int timeout )
		{			
			bool Success = m_MessageArrivedEvent.WaitOne( timeout, true );
			
			if( Success )
			{
				message = m_MessageReceived.GetString( );
			}
			else
			{
				message = String.Empty;
			}
			
			return Success;	
		}

		/// <summary>
		/// Sends a message to all the connected clients.
		/// </summary>
		/// <param name="message">A string message to send.</param>
		public void SendMessage( string message )
		{
			Debug.WriteLine( "MockRemoteServer::SendMessage in" );
			
			byte[ ] MessageBytes = MockRemoteServerMessage.GetBytes( message );
			
			lock( m_Clients.SyncRoot )
			{
				foreach( Socket ClientSocket in m_Clients )
				{
					int BytesSent = ClientSocket.Send( MessageBytes );
					
					Debug.WriteLine( "MockRemoteServer::SendMessage Sent message" );
					
					Debug.Assert( BytesSent == MessageBytes.Length,
						"Could not send the entire message in one go." );
				}
			}
			
			Debug.WriteLine( "MockRemoteServer::SendMessage out" );
		}
		
		/// <summary>
		/// Disconnects all connected clients.
		/// </summary>
		private void DisconnectAllClients( )
		{
			Debug.WriteLine( "MockRemoteServer::DisconnectAllClients Start" );
			
			foreach( Socket ClientSocket in m_Clients )
			{
				Debug.WriteLine( "MockRemoteServer::DisconnectAllClients closing down a socket" );
				ClientSocket.Shutdown( SocketShutdown.Both );
				ClientSocket.Close( );
			}
			
			m_Clients.Clear( );
			
			Debug.WriteLine( "MockRemoteServer::DisconnectAllClients End" );
			
		}
		
		/// <summary>
		/// Starts a tcp listener on the specified port.
		/// </summary>
		/// <param name="port">The listening port.</param>
		private void StartListening( int port )
		{
			Debug.WriteLine( "MockRemoteServer::StartListening start" );
			
			IPHostEntry HostEntry = Dns.GetHostEntry( "localhost" );
			
			IPAddress IPAddress = TcpSocket.GetIPAddress( HostEntry );

			m_Listener = new TcpListener( IPAddress, port );
			
			// Listen.
			m_Listener.Start( );
			
			Debug.WriteLine( "MockRemoteServer::StartListening end" );
		}
		
		/// <summary>
		/// Stops the listening socket.
		/// </summary>
		private void StopListening( )
		{
			Debug.WriteLine( "MockRemoteServer::StopListening start" );
			
			if( m_Listener != null )
			{
				m_Listener.Stop( );
				m_Listener = null;
			}
			
			Debug.WriteLine( "MockRemoteServer::StopListening end" );
		}
		
		/// <summary>
		/// Starts the thread that will process the server's client connections.
		/// </summary>
		private void StartProcessingThread( )
		{
			m_Running = true;
			
			m_Thread = new Thread( new ThreadStart( ThreadMain ) );
			
			m_Thread.Start( );
		}
		
		/// <summary>
		/// Stops the thread processing the client connections.
		/// </summary>
		private void StopProcessingThread( )
		{
			Debug.WriteLine( "MockRemoteServer::StopProcessingThread Start" );

			if( m_Thread != null )
			{
				m_Running = false;
				
				m_Thread.Join( );
				m_Thread = null;
			}

			Debug.WriteLine( "MockRemoteServer::StopProcessingThread End" );
		}
		
		/// <summary>
		/// Accepts, reads data from and sends data
		/// to the client connections.
		/// </summary>
		private void ThreadMain( )
		{
			Debug.WriteLine( "MockRemoteServer::ProcessClientConnections start" );
			
			while( m_Running )
			{			
				if( m_Listener.Pending( ) )
				{
					// Handle new client connections
					
					AcceptConnection( );
				}
				else
				{
					lock( m_Clients.SyncRoot )
					{
						if( m_Clients.Count > 0 )
						{
							// Handle client requests and responses.
							
							ProcessAllConnectedClients( );
						}
					}
				}
			}
			
			Debug.WriteLine( "MockRemoteServer::ProcessClientConnections end" );			
		}
		
		/// <summary>
		/// Accepts a new client connection.
		/// </summary>
		private void AcceptConnection( )
		{
			Debug.WriteLine( "MockRemoteServer::AcceptConnection start" );
			
			try
			{
				Socket NewClient = m_Listener.AcceptSocket( );
				
				lock( m_Clients.SyncRoot )
				{
					m_Clients.Add( NewClient );
				}

				Debug.WriteLine( "MockRemoteServer::AcceptConnection Added new client." );
				
				m_ConnectionEvent.Set( );
			}
			catch( Exception Ex )
			{
				Debug.WriteLine( String.Concat( "MockRemoteServer::AcceptConnection ", Ex.Message ) );
			}
			
			Debug.WriteLine( "MockRemoteServer::AcceptConnection end" );
		}
		
		/// <summary>
		/// Handles client requests and responses.
		/// </summary>
		private void ProcessAllConnectedClients( )
		{
			// Loop around the connected clients backwards so
			// we can safely remove a disconnected client from
			// the connection inside the loop.
			int CurrentClientIndex = m_Clients.Count;

			for( int i = 0; i < m_Clients.Count; ++i )
			{
				--CurrentClientIndex;

				Socket ClientSocket = m_Clients[ CurrentClientIndex ];

				ProcessClient( ClientSocket );
			}
		}
		
		/// <summary>
		/// Receives data from and sends data to a connected client socket.
		/// </summary>
		/// <param name="socket">The socket to process.</param>
		private void ProcessClient( Socket socket )
		{
			if( socket.Poll( 0, SelectMode.SelectRead ) )
			{
				// Read data.
				
				byte[ ] Bytes = new byte[ 4096 ];
				int BytesRead = socket.Receive( Bytes );
				
				if( BytesRead > 0 )
				{		
					if( m_MessageReceived.AddBytes( Bytes, BytesRead ) )
					{
						m_MessageArrivedEvent.Set( );
					}
				}
				else
				{
					// Client disconnected.
					
					m_Clients.Remove( socket );
					
					m_DisconnectionEvent.Set( );
				}	
			}
		}
	}
}
